#ifndef MTL_QUEUE_HPP
#define MTL_QUEUE_HPP

#include <queue>
#include "mutex.hpp"
#include "condition_variable.hpp"

namespace mtl {

  template<class T> class blocking_queue {
  public:
    typedef std::queue<T> queue_t;
    typedef typename queue_t::reference  reference;
    typedef typename queue_t::const_reference  const_reference;
    typedef typename queue_t::size_type  size_type;
    typedef typename queue_t::value_type value_type;
  public:
    blocking_queue() {}

    explicit blocking_queue(queue_t& q) : m_q(q) {}

    bool empty() const {
      lock_guard<mutex> lock(m_m);
      return m_q.empty();
    }

    size_type size() const {
      lock_guard<mutex> lock(m_m);
      return m_q.size();
    }

    reference front() {
      lock_guard<mutex> lock(m_m);
      wait_not_empty(lock);
      return m_q.front();
    }
    const_reference front() const {
      lock_guard<mutex> lock(m_m);
      wait_not_empty(lock);
      return m_q.front();
    }

    reference back() {
      lock_guard<mutex> lock(m_m);
      wait_not_empty(lock);
      return m_q.back();
    }
    const_reference back() const {
      lock_guard<mutex> lock(m_m);
      wait_not_empty(lock);
      return m_q.back();
    }

    void push(const value_type& v) {
      lock_guard<mutex> lock(m_m);
      m_q.push(v);
      m_not_empty.notify_one();
    }

    void pop() {
      lock_guard<mutex> lock(m_m);
      wait_not_empty(lock);
      m_q.pop();
    }

    void put(const value_type& v) {
      push(v);
    }

    value_type take() {
      lock_guard<mutex> lock(m_m);
      wait_not_empty(lock);
      reference v = m_q.front();
      m_q.pop();
      return v;
    }

    void clear() {
      lock_guard<mutex> lock(m_m);
      while (!m_q.empty()) {
        m_q.pop();
      }
    }
  protected:
    template<class _Lock> void wait_not_empty(_Lock& lock) const {
      while (m_q.empty()) {
        m_not_empty.wait(lock);
      }
    }
  private:
    queue_t       m_q;
    mutable mutex m_m;
    mutable condition_variable  m_not_empty;
  };
}
#endif

